iT邦幫忙

2023 iThome 鐵人賽

DAY 30
0
Security

從自建漏洞中學習 - 一起填坑吧系列 第 30

Auth 應用程式 - 儲存 JWT 在 Cookie

  • 分享至 

  • xImage
  •  

Auth 應用程式 - 儲存 JWT 在 Cookie

儲存 JWT 在 Cookie

還記得我們之前有提到 XSS 攻擊嗎 ?

可以參考先前的文章: https://ithelp.ithome.com.tw/articles/10322728

在傳送 JWT 的時候,如果在瀏覽器中不是使用 Cookie 而是使用 LocalStorage 來儲存 JWT,可能會讓 token 暴露在 XSS 攻擊的風險中。

為什麼呢?

原因是因為:

如果 JavaScript 可以存取 JWT,那麼任何可以將 script inject 到網站的攻擊者都可以嘗試做他想做的事情。

設定 Cookie 安全設置

const createSendToken = (user, statusCode, res) => {
    const token = signToken(user._id);
    
    // 注意看以下重點: 設置屬性
    // response cookie 並設置一些屬性
    res.cookie('jwt', token, {
       expires: new Date(Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000),
        secure: true,
        httpOnly: true
    });
    
    res.status(statusCode).json({
        status: 'success',
        token,
        data: {
            user
        }
    });
    
}

說明:

  • secure: true

    設置 secure 為 true 的原因是因為: 我們可以透過設置它,避免 Browser 將 cookie 與未加密的 http (也就是非 https) 請求一起傳送到我們的網域 (只允許有 https 的請求)。

    所以,在設置的時候可以注意一下以下兩點:

    1. 在有設置 https 的情況下,我們可以直接設置 secure: true

    2. 若是沒有設置 https 的情況下設置它,會導致 cookie 無法成功建置,這樣就會出問題。

  • httpOnly: true

    設置 httpOnly 為 true 的原因是因為: 我們可以透過設置它,可以讓 Cookie 無法被 Browser 以任何方式訪問或是修改。

修改一下程式碼

不過上述這樣寫如果在測試環境中 run 起來 (local 中 run 起來) 會有些問題,所以可以改寫成如下:

const createSendToken = (user, statusCode, res) => {
    const token = signToken(user._id);
    
    const cookieOptions = {
        expires: new Date(Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000),
        httpOnly: true 
    }
    
    // 在 prodction 環境下設置 secure: true
    if (process.env.NODE_ENV === 'production') {
        cookieOptions.secure = true;
    }
    
    // 若在測試環境下不會特別設置 secure,若在 prodction 環境下則會設置 secure: true
    res.cookie('jwt', token, cookieOptions);
    
    res.status(statusCode).json({
        status: 'success',
        token,
        data: {
            user
        }
    });
    
}

設置 Cookie 屬性就沒問題了嗎?

雖然我們使用了 Cookie ,也設定了 secure 和 httpOnly,但是這並不代表就不會有其他潛在風險。

讓我們來看看 CSRF 吧 ~

在 OWASP 網站上, CSRF 的 overview 如下:

Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing.
If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.

跨站請求偽造 (CSRF) 是一種迫使用戶在其當前經過身份驗證的 Web 應用程式上執行不必要的操作的攻擊。

採用 cookie 的方式進行登入驗證,在通過驗證後,Client 會拿到 cookie ,代表之後就不需要做重複驗證了。然而,當 hacker 偷偷拿到了 token ,代表你去操作系統,這不就慘了?

這也正是所謂的 CSRF

在我們先前的實作中,針對 JWT ,我們只有儲存 JWT 在 Cookie 中,並且進行了一些屬性設置,其餘的事情並沒有多做處理。

這也就代表著 - 如果 "將 JWT 與每個 HTTP 請求一起傳送,則駭客能夠代表該 user 傳送請求"。

那麼我們也就暴露在 CSRF 的風險中。

除了設置 Cookie 屬性,我們還能做什麼?

我們可以 ... 設定 SameSite 屬性

CSRF 攻擊中,攻擊者跨過 Domain 並向網站 request 資料,而若是該網站沒有對 Cookie 進行再度驗證或是確認就接受 request,攻擊者就可以代表該使用者去做任何事情了。

在 Server 中設置 SameSite 屬性,我們可以過濾掉一些不是來自於我們 Domain 的 request,從根本上解決 CSRF 問題 ~

SameSite 介紹

SameSite 有幾個設定值,可以根據你的網站特性做安全上的設置。

SameSite=Strict

一些需要比較高強度安全性的網站,像是金流網站,我們可以考慮設置 Strict

Strict 是最嚴格的設定,只允許相同網域的 request,是完全禁止第三方的 Cookie 的。

我們可以修改先前的 cookieOptions,加入 sameSite:

const cookieOptions = {
     expires: new Date(Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000),
      httpOnly: true,
      // here
      sameSite: 'strict'
}

SameSite=Lax

Lax 為較鬆散的設定值,若把 SameSite 設置為 Lax,除了下述請求可以傳送 Cookie,其他則禁止。

範例也是修改 sameSite 值:

const cookieOptions = {
     expires: new Date(Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000),
      httpOnly: true,
      // here
      sameSite: 'lax'
}

如果你是需要允許以下請求類型的網站,你可以使用這個設定值。

允許傳送 Cookie 的請求類型:

  • 連結

    <a href='your_link'></a>

  • prerender 預先渲染

    <link rel="prerender" href="your_link"/>

  • Get form

    <form method="GET" action="...">

不過,需要注意的是,這個設定會 "允許 Form 做 GET 時,傳送 Cookie" 。

需要注意的重點:

在進行 GET 實作時,還是要確保只讓 get 裡面實作的行為真的只做取值的動作。假設今天 API 在 GET 時,會去 update 一些資料,那麼就有可能讓 hacker 有機可趁!

SameSite=None

None 為不限制 Cookie 傳送

需要注意的是:

在經過考量後決定要設置為 None 的話,需要將之前的 Secure 設為 true, 其設定才會生效。

範例就如同先前的設定,並把 sameSite 設置為 none

其他設定

除了 Cookie + SameSite 設定外,若有需要的話,還有以下可以考慮採用以下的方式:

  • CSRF token

    加入一個 CSRF token 的防禦,request 要執行動作時, client 需要帶上一組後端產生的 token 做驗證,通過後才會執行請求。

    在 Express 中,可以考慮使用 csurf 相關的套件來解決問題,但官方已經將其標記為 deprecated,有興趣的讀者可以自行查詢其他套件來使用~

    在此還是附上 csurf 連結 : https://www.npmjs.com/package/csurf


今日小心得

這次三十天的介紹就到這邊啦~ 資訊滿零散的,不過藉此我也學習到很多。

在這次的 Auth 實作還有很多沒做的,也有一些資安上的處理沒有被探討到。

這次感覺上沒有細講完,也還有很多 OAuth 相關的東西沒讀完,總覺得學無止境!

不過,我想這只是一個起點吧! 未來應該會建立網站繼續記錄這些學習到的東西,繼續加油 <3


Reference


上一篇
Auth 應用程式 - 更新 user data 篇
系列文
從自建漏洞中學習 - 一起填坑吧30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言